"""
Text parsing utilities for document analysis.

Provides comprehensive text parsing capabilities for various document formats
with support for Chinese text processing, structure extraction, and content analysis.
"""

import re
import hashlib
from pathlib import Path
from typing import Dict, List, Optional, Any, Tuple, Union
from dataclasses import dataclass
import logging

# Try to import optional dependencies
try:
    import markdown
    MARKDOWN_AVAILABLE = True
except ImportError:
    MARKDOWN_AVAILABLE = False

try:
    import jieba
    JIEBA_AVAILABLE = True
except ImportError:
    JIEBA_AVAILABLE = False

try:
    import spacy
    SPACY_AVAILABLE = True
except ImportError:
    SPACY_AVAILABLE = False

logger = logging.getLogger(__name__)


@dataclass
class TextSection:
    """Represents a section of parsed text."""
    title: str
    content: str
    level: int  # Heading level (1-6)
    line_start: int
    line_end: int
    word_count: int = 0


@dataclass
class ParsedDocument:
    """Represents a fully parsed document with structure and metadata."""
    raw_text: str
    sections: List[TextSection]
    headings: List[Tuple[str, int, int]]  # (text, level, line_number)
    links: List[Tuple[str, str]]  # (text, url)
    code_blocks: List[str]  # List of code block contents
    metadata: Dict[str, Any]
    language: str = "zh"

    def get_full_text(self) -> str:
        """Get full text content excluding code blocks.

        Returns:
            Clean text content
        """
        # Remove code blocks from text
        clean_text = self.raw_text
        for code_block in self.code_blocks:
            clean_text = clean_text.replace(code_block, "")
        return clean_text.strip()

    def get_word_count(self) -> int:
        """Get total word count.

        Returns:
            Total word count
        """
        return len(self._tokenize_text(self.get_full_text()))

    def get_section_by_title(self, title: str) -> Optional[TextSection]:
        """Find section by title.

        Args:
            title: Section title to find

        Returns:
            Matching section or None
        """
        for section in self.sections:
            if section.title.lower() == title.lower():
                return section
        return None

    def _tokenize_text(self, text: str) -> List[str]:
        """Tokenize text based on available libraries.

        Args:
            text: Text to tokenize

        Returns:
            List of tokens
        """
        if self.language == "zh" and JIEBA_AVAILABLE:
            return list(jieba.cut(text))
        else:
            # Basic tokenization for non-Chinese text
            return re.findall(r'\b\w+\b', text)


class TextParser:
    """Advanced text parser with support for multiple formats and languages."""

    def __init__(self, language: str = "zh", config: Dict[str, Any] = None):
        """Initialize text parser.

        Args:
            language: Primary language for text processing
            config: Parser configuration
        """
        self.language = language
        self.config = config or {}

        # Initialize NLP components if available
        self.nlp_model = None
        if SPACY_AVAILABLE and language == "zh":
            try:
                self.nlp_model = spacy.load("zh_core_web_sm")
                logger.info("Chinese spacy model loaded")
            except OSError:
                logger.warning("Chinese spacy model not available. Install with: python -m spacy download zh_core_web_sm")

        # Initialize jieba if available
        if JIEBA_AVAILABLE and language == "zh":
            # Add custom dictionary if provided
            custom_dict = self.config.get("custom_dictionary")
            if custom_dict and Path(custom_dict).exists():
                jieba.load_userdict(custom_dict)
                logger.info(f"Custom jieba dictionary loaded: {custom_dict}")

        # Compile regex patterns
        self._compile_patterns()

    def _compile_patterns(self):
        """Compile regex patterns for text processing."""
        # Markdown patterns
        self.heading_pattern = re.compile(r'^(#{1,6})\s+(.+)$', re.MULTILINE)
        self.link_pattern = re.compile(r'\[([^\]]+)\]\(([^)]+)\)')
        self.code_block_pattern = re.compile(r'```[\s\S]*?```')
        self.inline_code_pattern = re.compile(r'`([^`]+)`')
        self.list_pattern = re.compile(r'^\s*[-*+]\s+(.+)$', re.MULTILINE)
        self.numbered_list_pattern = re.compile(r'^\s*\d+\.\s+(.+)$', re.MULTILINE)

        # Chinese text patterns
        self.chinese_punctuation_pattern = re.compile(r'[，。！？；：""''（）【】《》]')
        self.chinese_sentence_pattern = re.compile(r'[。！？]')
        self.ambiguous_words_pattern = re.compile(
            r'(可能|大概|或许|应该|通常|一般|相关|适当|基本|主要|重要|必要)'
        )
        self.vague_quantifiers_pattern = re.compile(
            r'(多个|一些|若干|许多|大量|少数)'
        )

    def parse_file(self, file_path: Path) -> ParsedDocument:
        """Parse document from file.

        Args:
            file_path: Path to document file

        Returns:
            Parsed document
        """
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()
        except UnicodeDecodeError:
            # Try alternative encodings
            try:
                with open(file_path, 'r', encoding='gbk') as f:
                    content = f.read()
            except UnicodeDecodeError as e:
                raise ValueError(f"Unable to decode file {file_path}: {e}")

        return self.parse_text(content, file_path.suffix)

    def parse_text(self, text: str, file_extension: str = ".md") -> ParsedDocument:
        """Parse text content.

        Args:
            text: Text content to parse
            file_extension: File extension for format detection

        Returns:
            Parsed document
        """
        # Detect format and parse accordingly
        if file_extension.lower() in ['.md', '.markdown']:
            return self._parse_markdown(text)
        else:
            return self._parse_plain_text(text)

    def _parse_markdown(self, text: str) -> ParsedDocument:
        """Parse markdown text.

        Args:
            text: Markdown text

        Returns:
            Parsed document
        """
        # Extract metadata if markdown extensions available
        metadata = {}
        if MARKDOWN_AVAILABLE:
            try:
                md = markdown.Markdown(extensions=['meta', 'toc'])
                html = md.convert(text)
                if hasattr(md, 'Meta') and md.Meta:
                    metadata = {k: v[0] if len(v) == 1 else v for k, v in md.Meta.items()}
            except Exception as e:
                logger.warning(f"Markdown parsing failed: {e}")

        # Extract structure
        headings = self._extract_headings(text)
        sections = self._extract_sections(text, headings)
        links = self._extract_links(text)
        code_blocks = self._extract_code_blocks(text)

        return ParsedDocument(
            raw_text=text,
            sections=sections,
            headings=headings,
            links=links,
            code_blocks=code_blocks,
            metadata=metadata,
            language=self.language
        )

    def _parse_plain_text(self, text: str) -> ParsedDocument:
        """Parse plain text.

        Args:
            text: Plain text content

        Returns:
            Parsed document
        """
        # For plain text, create simple sections based on paragraphs
        sections = []
        paragraphs = text.split('\n\n')

        for i, paragraph in enumerate(paragraphs):
            if paragraph.strip():
                section = TextSection(
                    title=f"Section {i+1}",
                    content=paragraph.strip(),
                    level=1,
                    line_start=text.find(paragraph),
                    line_end=text.find(paragraph) + len(paragraph)
                )
                sections.append(section)

        return ParsedDocument(
            raw_text=text,
            sections=sections,
            headings=[],
            links=[],
            code_blocks=[],
            metadata={},
            language=self.language
        )

    def _extract_headings(self, text: str) -> List[Tuple[str, int, int]]:
        """Extract headings from text.

        Args:
            text: Text to analyze

        Returns:
            List of (heading_text, level, line_number) tuples
        """
        headings = []
        lines = text.split('\n')

        for line_num, line in enumerate(lines, 1):
            match = self.heading_pattern.match(line)
            if match:
                level = len(match.group(1))
                heading_text = match.group(2).strip()
                headings.append((heading_text, level, line_num))

        return headings

    def _extract_sections(self, text: str, headings: List[Tuple[str, int, int]]) -> List[TextSection]:
        """Extract sections based on headings.

        Args:
            text: Full text
            headings: List of headings

        Returns:
            List of text sections
        """
        sections = []
        lines = text.split('\n')

        if not headings:
            # Create single section from entire text
            return [TextSection(
                title="Document",
                content=text.strip(),
                level=0,
                line_start=0,
                line_end=len(lines)
            )]

        for i, (heading_text, level, line_num) in enumerate(headings):
            # Find section boundaries
            start_line = line_num - 1  # Convert to 0-based
            end_line = len(lines)

            if i < len(headings) - 1:
                next_heading_line = headings[i + 1][2] - 1
                end_line = next_heading_line

            # Extract section content
            section_lines = lines[start_line:end_line]
            content = '\n'.join(section_lines).strip()

            # Remove heading from content
            if content.startswith('#' * level + ' ' + heading_text):
                content = content[len('#' * level + ' ' + heading_text):].strip()

            section = TextSection(
                title=heading_text,
                content=content,
                level=level,
                line_start=start_line,
                line_end=end_line
            )
            sections.append(section)

        return sections

    def _extract_links(self, text: str) -> List[Tuple[str, str]]:
        """Extract links from text.

        Args:
            text: Text to analyze

        Returns:
            List of (link_text, url) tuples
        """
        return self.link_pattern.findall(text)

    def _extract_code_blocks(self, text: str) -> List[str]:
        """Extract code blocks from text.

        Args:
            text: Text to analyze

        Returns:
            List of code block contents
        """
        return self.code_block_pattern.findall(text)

    def analyze_text_quality(self, text: str) -> Dict[str, Any]:
        """Analyze text quality metrics.

        Args:
            text: Text to analyze

        Returns:
            Quality metrics dictionary
        """
        # Basic statistics
        sentences = self._split_sentences(text)
        words = self._tokenize_text(text)
        paragraphs = [p for p in text.split('\n\n') if p.strip()]

        # Language-specific analysis
        if self.language == "zh":
            chinese_chars = len(re.findall(r'[\u4e00-\u9fff]', text))
            punctuation_count = len(self.chinese_punctuation_pattern.findall(text))
        else:
            chinese_chars = 0
            punctuation_count = len(re.findall(r'[.!?;:,]', text))

        # Detect issues
        ambiguous_phrases = self._find_ambiguous_phrases(text)
        vague_quantifiers = self._find_vague_quantifiers(text)

        return {
            "character_count": len(text),
            "word_count": len(words),
            "sentence_count": len(sentences),
            "paragraph_count": len(paragraphs),
            "avg_sentence_length": len(words) / max(len(sentences), 1),
            "avg_word_length": sum(len(word) for word in words) / max(len(words), 1),
            "chinese_character_count": chinese_chars,
            "punctuation_count": punctuation_count,
            "ambiguous_phrases_count": len(ambiguous_phrases),
            "vague_quantifiers_count": len(vague_quantifiers),
            "ambiguous_phrases": ambiguous_phrases[:10],  # Limit to first 10
            "vague_quantifiers": vague_quantifiers[:10],  # Limit to first 10
            "language_detected": self.language
        }

    def _split_sentences(self, text: str) -> List[str]:
        """Split text into sentences.

        Args:
            text: Text to split

        Returns:
            List of sentences
        """
        if self.language == "zh":
            # Chinese sentence splitting
            sentences = self.chinese_sentence_pattern.split(text)
        else:
            # English sentence splitting
            sentences = re.split(r'[.!?]+', text)

        return [s.strip() for s in sentences if s.strip()]

    def _tokenize_text(self, text: str) -> List[str]:
        """Tokenize text based on language.

        Args:
            text: Text to tokenize

        Returns:
            List of tokens
        """
        if self.language == "zh" and JIEBA_AVAILABLE:
            return list(jieba.cut(text))
        else:
            return re.findall(r'\b\w+\b', text)

    def _find_ambiguous_phrases(self, text: str) -> List[str]:
        """Find ambiguous phrases in text.

        Args:
            text: Text to analyze

        Returns:
            List of ambiguous phrases with context
        """
        ambiguous_phrases = []

        for match in self.ambiguous_words_pattern.finditer(text):
            start = max(0, match.start() - 20)
            end = min(len(text), match.end() + 20)
            context = text[start:end].strip()
            ambiguous_phrases.append({
                "word": match.group(),
                "context": context,
                "position": match.start()
            })

        return ambiguous_phrases

    def _find_vague_quantifiers(self, text: str) -> List[str]:
        """Find vague quantifiers in text.

        Args:
            text: Text to analyze

        Returns:
            List of vague quantifiers with context
        """
        vague_quantifiers = []

        for match in self.vague_quantifiers_pattern.finditer(text):
            start = max(0, match.start() - 20)
            end = min(len(text), match.end() + 20)
            context = text[start:end].strip()
            vague_quantifiers.append({
                "quantifier": match.group(),
                "context": context,
                "position": match.start()
            })

        return vague_quantifiers

    def extract_keywords(self, text: str, max_keywords: int = 20) -> List[Tuple[str, float]]:
        """Extract keywords from text.

        Args:
            text: Text to analyze
            max_keywords: Maximum number of keywords to return

        Returns:
            List of (keyword, score) tuples
        """
        # Simple keyword extraction based on frequency
        words = self._tokenize_text(text)
        word_freq = {}

        for word in words:
            if len(word) > 2:  # Skip very short words
                word_freq[word] = word_freq.get(word, 0) + 1

        # Sort by frequency and return top keywords
        sorted_words = sorted(word_freq.items(), key=lambda x: x[1], reverse=True)
        return sorted_words[:max_keywords]

    def calculate_readability_score(self, text: str) -> float:
        """Calculate basic readability score.

        Args:
            text: Text to analyze

        Returns:
            Readability score (0-100, higher = more readable)
        """
        sentences = self._split_sentences(text)
        words = self._tokenize_text(text)

        if not sentences or not words:
            return 0.0

        # Basic readability metrics
        avg_sentence_length = len(words) / len(sentences)
        avg_word_length = sum(len(word) for word in words) / len(words)

        # Simple scoring formula (adjust based on language)
        if self.language == "zh":
            # Chinese readability
            score = max(0, 100 - (avg_sentence_length - 15) * 2 - (avg_word_length - 2) * 5)
        else:
            # English readability
            score = max(0, 100 - (avg_sentence_length - 20) * 1.5 - (avg_word_length - 5) * 2)

        return min(100, max(0, score))

    def generate_text_hash(self, text: str) -> str:
        """Generate hash for text content.

        Args:
            text: Text to hash

        Returns:
            MD5 hash of text
        """
        return hashlib.md5(text.encode('utf-8')).hexdigest()

    def is_chinese_text(self, text: str) -> bool:
        """Check if text is primarily Chinese.

        Args:
            text: Text to check

        Returns:
            True if primarily Chinese
        """
        chinese_chars = len(re.findall(r'[\u4e00-\u9fff]', text))
        total_chars = len(re.sub(r'\s', '', text))

        if total_chars == 0:
            return False

        chinese_ratio = chinese_chars / total_chars
        return chinese_ratio > 0.3

    def detect_language(self, text: str) -> str:
        """Detect language of text.

        Args:
            text: Text to analyze

        Returns:
            Detected language code
        """
        if self.is_chinese_text(text):
            return "zh"
        else:
            return "en"